package testApplets;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.vecmath.Point3f;

import com.sun.j3d.utils.geometry.GeometryInfo;

import processing.core.PApplet;
import simpleGeom.*;
import triangulate2D.DelaunayTriangle;
import triangulate2D.Poly2Tri;
import triangulate2D.Polygon;
import triangulate2D.PolygonPoint;

public class TriangulateSplitApplet extends PApplet {
	String path="/Users/caad/Desktop/Grotto - First Half V/03_scaled/";
	Mesh meshInput;
	
	float maxy1=0.0055f+0.0011f;
	float maxy2=3.2175f-0.0011f;
	float mz1=-1.5195f*1000f;
	float mz2=0;
	//float[]cutsValues=new float[]{0.0066f,0.407825f,0.80905f,1.210275f,1.6115f,2.0127249f,2.41395f,2.8151748f,3.2164f};
	float[] cutsZValues=new float[]{-1.5195f,mz1*0.5f+0.000171f,0};
	float[]cutsValues=new float[]{0.0066f,0.407825f,0.80905f,1.210275f,1.6115f,2.0127249f,2.41276f,2.81617481f,3.21717f};
	
	public void scale(){
		meshInput = new Mesh(this.selectInput());
		meshInput.scale(1000,1000, 1000);
		meshInput.saveMesh(this.selectOutput());
	}
	public void reduceMesh(float z){
		meshInput = new Mesh(this.selectInput());
		meshInput.construct();
		ArrayList<MeshFace>newfaces=new ArrayList<MeshFace>();
		for (int i=0;i<meshInput.getNFaces();i++){
			MeshFace face=meshInput.getFace(i);
			int nUp=0;
			int nDown=0;
			
			for (int j=0;j<face.getNNodes();j++){
				MeshNode n=face.getNode(j);
				if (n.getZ()>0){
					nUp++;
				}
				else{
					nDown++;
				}
			}
			if (nUp>0&&nDown>0){
				newfaces.add(face);
			}
		}
		ArrayList<MeshFace>newnewfaces=new ArrayList<MeshFace>();
		for (int i=0;i<newfaces.size();i++){
			MeshFace face=newfaces.get(i);
			ArrayList<MeshEdge>edges=face.getEdges();
			ArrayList<MeshNode>cutNodes=new ArrayList<MeshNode>();
			for (int j=0;j<edges.size();j++){
				MeshEdge e=edges.get(j);
				
				float dZ=e.getZ2()-e.getZ1();
				float f=(z-e.getZ1())/dZ;
				if (f>=0&&f<=1){
					float vX=(e.getX2()-e.getX1())*f+e.getX1();
					float vY=(e.getY2()-e.getY1())*f+e.getY1();
					MeshNode node=new MeshNode(vX,vY,z);
					if (contains(cutNodes,node)){
						newnewfaces.add(face);
					}
					else{
						cutNodes.add(node);
					}
				}
			}
			/*if (abs(face.getArea())<0.0000001f){
				println("noArea");
			}
			else{
				newfaces.remove(i);
				i--;
			}*/
		}
		meshInput.setFaces(newnewfaces);
		meshInput.removeUnusedNodes();
		meshInput.saveMesh(this.selectOutput());
	}
	public void setup() {
		/*meshInput = new Mesh(this.selectInput());
		Box3D mBds=meshInput.getBounds();
		println(mBds);
		Plane plane=new Plane(new Vec3D(0,3.1555f,0), new Vec3D(0,-1,0));//top
		//clipPlane(plane,null);
		plane=new Plane(new Vec3D(0,-0.0499f,0), new Vec3D(0,1,0));//bottom
		//plane=new Plane(new Vec3D(1.49011f,0.0,0), new Vec3D(-1,0,0));//back
		//plane=new Plane(new Vec3D(0.0016f,0.0,0), new Vec3D(1,0,0));//front
		clipPlane(plane,null);
		meshInput.saveMesh(this.selectOutput());*/
		
		float dY=maxy2-maxy1;
		float stepY=dY/8f;
		String cuts="";
		for (int i=0;i<9;i++){
			cuts+=""+(i*stepY+maxy1)+"f,";
		}
		println(cuts);
		//scale();
		trimY(false,cutsValues[1]);
		//reduceMesh(cutsZValues[1]);
		//trimZ(true,cutsZValues[1]);
		//trimY(false,cutsValues[8]);
		this.exit();
	}
	
	
	public void saveRingsAsVSS(ArrayList<ArrayList<MeshNode>>rings){
		SimpleVSSExporterBeginRaw newExport=new SimpleVSSExporterBeginRaw(500,500,this,this.selectOutput());
		for (int i=0;i<rings.size();i++){
			ArrayList<MeshNode>ring=rings.get(i);
			newExport.beginShape();
			for (int j=0;j<ring.size();j++){
				MeshNode node=ring.get(j);
				newExport.vertex(node.x, node.y);
			}
			if (ring.size()==2){
				MeshNode node=ring.get(0);
				newExport.vertex(node.y, node.z);
			}
			newExport.endShape(CLOSE);
		}
		newExport.dispose();
	}
	public void saveTriangles(GeometryInfo gi,SimpleVSSExporterBeginRaw newExport){
		
		
		int[] indexes = gi.getCoordinateIndices();
		Point3f[] coor=gi.getCoordinates();
		Point3f[] pts = gi.getCoordinates();
		println("endTriangulate");
		for (int i = 0; i < indexes.length; i += 3) {
			newExport.beginShape();
			for (int j=i;j<i+3;j++){
				Point3f pt=coor[indexes[j]];
				newExport.vertex(pt.x, pt.y);
			}
			newExport.endShape(CLOSE);
		}
		
		
		
	}
	
	public void trimZ(boolean up,float y){
		Mesh mesh = new Mesh(this.selectInput());
		mesh.triangulate();
		mesh.construct();
		int edgesNoLength=0;
		for (int i=0;i<mesh.getNEdges();i++){
			MeshEdge mE=mesh.getEdge(i);
			if (isEquals(mE.getNode1(),mE.getNode2())){
				//println("noLength Edge"+mE.getId());
				edgesNoLength++;
				
			}
		}
		println("edgesNoLength: "+edgesNoLength);
		
		trim(mesh,up,y);
		
		mesh.saveMesh(this.selectOutput());
	}
	public void trimY(boolean up,float y){
		Mesh mesh = new Mesh(this.selectInput());
		mesh.construct();
		int edgesNoLength=0;
		for (int i=0;i<mesh.getNEdges();i++){
			MeshEdge mE=mesh.getEdge(i);
			if (isEquals(mE.getNode1(),mE.getNode2())){
				//println("noLength Edge"+mE.getId());
				edgesNoLength++;
				
			}
		}
		println("edgesNoLength: "+edgesNoLength);
		mesh.YToZ();
		trim(mesh,up,y);
		mesh.YToZ();
		mesh.saveMesh(this.selectOutput());
	}
	
	public void trim(Mesh mesh, boolean up, float z) {
		mesh.trimMeshZ(up, z);
		ArrayList<ArrayList<MeshNode>> rings = collectBordersZ(mesh, z);
		int[] nContained = new int[rings.size()];
		for (int i = 0; i < rings.size(); i++) {
			ArrayList<MeshNode> ring1 = rings.get(i);
			MeshNode firstNode = ring1.get(0);
			nContained[i] = 0;
			for (int j = 0; j < rings.size(); j++) {
				if (j != i) {
					ArrayList<MeshNode> ring2 = rings.get(j);
					if (Polygon2D.contains(firstNode.x, firstNode.y, ring2)) {
						nContained[i]++;
					}
				}
			}
		}
		
		//saveRingsAsVSS(rings);
		//SimpleVSSExporterBeginRaw  newExport=new SimpleVSSExporterBeginRaw(500,500,this,this.selectOutput());
		for (int i = 0; i < rings.size(); i++) {
			if (nContained[i] % 2 == 0) {
				ArrayList<ArrayList<MeshNode>> allRings = new ArrayList<ArrayList<MeshNode>>();
				ArrayList<MeshNode> ring = rings.get(i);
				
				allRings.add(ring);
				for (int j = 0; j < rings.size(); j++) {
					if (nContained[j] == nContained[i] + 1) {
						ArrayList<MeshNode> ring2 = rings.get(j);
						
						if (ring != ring2) {
							MeshNode n = ring2.get(0);
							if (Polygon2D.contains(n.x, n.y, ring)) {
								allRings.add(ring2);
							}
						}
					}
				}
				fillPoly2Tri(mesh, allRings,up);
				//GeometryInfo gi=fillRings(mesh, allRings,up);
				//saveTriangles(gi,newExport);
				
			}
		}
		//newExport.dispose();
	}
	
	public ArrayList<ArrayList<MeshNode>> collectBordersZ(Mesh mesh, float z){
		mesh.construct();
		int edgesNoLength=0;
		
		for (int i=0;i<mesh.getNEdges();i++){
			MeshEdge mE=mesh.getEdge(i);
			if (isEquals(mE.getNode1(),mE.getNode2())){
				//println("noLength Edge"+mE.getId());
				edgesNoLength++;
				/*println(mE.getNode1());
				println(mE.getNode2());
				*moveToCenter(mE.getNode1());
				moveToCenter(mE.getNode2());
				println("after");
				println(mE.getNode1());
				println(mE.getNode2());
				if (mE.getNode1()==mE.getNode2()){
					println("the same");
					
				}*/
				
				println("after moving: "+isEquals(mE.getNode1(),mE.getNode2()));
			}
		}
		println("edgesNoLength: "+edgesNoLength);
		for (int i=0;i<mesh.getNNodes();i++){
			MeshNode node=mesh.getNode(i);
			node.setId(-1);
		}
		ArrayList<MeshNode>newNodes=new ArrayList<MeshNode>();
		for (int i=0;i<mesh.getNNodes();i++){
			MeshNode node=mesh.getNode(i);
			if (node.getZ()==z){
				node.setId(newNodes.size());
				newNodes.add(node);
			}
		}
		boolean[]collected=new boolean[newNodes.size()];
		ArrayList<ArrayList<MeshNode>>rings=new ArrayList<ArrayList<MeshNode>>();
		for (int i=0;i<newNodes.size();i++){
			MeshNode node=newNodes.get(i);
			if (!collected[node.getId()]){// node is a border && node is not yet added
				ArrayList<MeshNode>ring=new ArrayList<MeshNode>();
				rings.add(ring);
				ring.add(node);
				collected[node.getId()]=true;
				while(node!=null){
					MeshNode[]nbs=node.getNbs();
					node=null;
					for (int j=0;j<nbs.length;j++){
						MeshNode nb=nbs[j];
						if (nb.id>=0&&!collected[nb.getId()]){// node is a border && node is not yet added
							if (contains(ring,nb)){
								println("contained");
							}
							ring.add(nb);
							collected[nb.getId()]=true;
							node=nb;
							j=nbs.length;
						}
					}
				}
				
			}
		}
		return rings;
		
	}
	public void clipPlane(Plane plane,ArrayList<MeshNode>newRing){
		ArrayList<ArrayList<MeshNode>>rings=meshInput.trimMeshGetBorders(plane);
		/*SimpleVSSExporterBeginRaw newExport=new SimpleVSSExporterBeginRaw(500,500,this,this.selectOutput());
		for (int i=0;i<rings.size();i++){
			ArrayList<MeshNode>ring=rings.get(i);
			newExport.beginShape();
			for (int j=0;j<ring.size();j++){
				MeshNode node=ring.get(j);
				newExport.vertex(node.y, node.z);
			}
			if (ring.size()==2){
				MeshNode node=ring.get(0);
				newExport.vertex(node.y, node.z);
			}
			newExport.endShape(CLOSE);
		}
		newExport.dispose();
		*/
		for (int i=0;i<rings.size();i++){
			ArrayList<MeshNode>ring=rings.get(i);
			for (int j=0;j<ring.size();j++){
				MeshNode node=ring.get(j);
				//YZtoXY(node);
			}
		}
		ArrayList<ArrayList<MeshNode>>polygons=new ArrayList<ArrayList<MeshNode>>();
		for (int i=0;i<rings.size();i++){
			ArrayList<MeshNode>ring1=rings.get(i);
			MeshNode firstNode=ring1.get(0);
			boolean contained=false;
			for (int j=0;j<rings.size();j++){
				if (j!=i){
					ArrayList<MeshNode> ring2=rings.get(j);
					if (Polygon2D.contains(firstNode.x, firstNode.y, ring2)){
						contained=true;
						j=rings.size();
					}
				}
			}
			if (!contained){
				polygons.add(ring1);
			}
		}
		saveRingsAsVSS(polygons);
		println("polygons"+polygons.size());
		for (int i = 0; i < polygons.size(); i++) {
			ArrayList<ArrayList<MeshNode>> allRings = new ArrayList<ArrayList<MeshNode>>();
			ArrayList<MeshNode> ring = polygons.get(i);
			allRings.add(ring);
			for (int j = 0; j < rings.size(); j++) {
				ArrayList<MeshNode> ring2 = rings.get(j);
				if (ring != ring2) {
					MeshNode n = ring2.get(0);
					if (Polygon2D.contains(n.x, n.y, ring)) {
						allRings.add(ring2);
					}
				}
			}
			fillRings(meshInput,allRings,true);
		}
		for (int i=0;i<rings.size();i++){
			ArrayList<MeshNode>ring=rings.get(i);
			for (int j=0;j<ring.size();j++){
				MeshNode node=ring.get(j);
				//XYtoYZ(node);
			}
		}
		
	}
	public void fillPoly2Tri(Mesh meshInput,ArrayList<ArrayList<MeshNode>> rings,boolean up){
		ArrayList<MeshNode> nodes = new ArrayList<MeshNode>();
		for (int i = 0; i < rings.size(); i++) {
			ArrayList<MeshNode> ring = rings.get(i);
			if (i==0&&Polygon2D.getArea(ring)<0){
				Collections.reverse(ring);
			}
			if (i>0&&Polygon2D.getArea(ring)>0){
				Collections.reverse(ring);
			}
			
		}
		
		ArrayList<MeshNode> firstring = rings.get(0);
		ArrayList<PolygonPoint> polyPts = new ArrayList<PolygonPoint>();
		for (int j = 0; j < firstring.size(); j++) {
			MeshNode node = firstring.get(j);
			polyPts.add(new PolyPt(node.x*1000f,node.y*1000f,node));
		}
		Polygon poly=new Polygon(polyPts);
		println("rings.size(): "+rings.size());
		for (int i = 1; i < rings.size(); i++) {
			ArrayList<MeshNode> ring = rings.get(i);
			 polyPts = new ArrayList<PolygonPoint>();
			for (int j = 0; j < ring.size(); j++) {
				MeshNode node = ring.get(j);
				polyPts.add(new PolyPt(node.x,node.y,node));
			}
			Polygon hole=new Polygon(polyPts);
			poly.addHole(hole);
			
		}
		
		Poly2Tri.triangulate(poly);
		List<DelaunayTriangle> triangles=poly.getTriangles();
		for (int i=0;i<triangles.size();i++){
			DelaunayTriangle tri=triangles.get(i);
			PolyPt p1=(PolyPt)tri.points[0];
			PolyPt p2=(PolyPt)tri.points[1];
			PolyPt p3=(PolyPt)tri.points[2];
			meshInput.addTri(p1.node, p2.node, p3.node);
			
		}
		println("triangles: "+triangles.size());
	}
	public boolean contains(ArrayList<MeshNode>nodes,MeshNode node){
		for (int i=0;i<nodes.size();i++){
			MeshNode cNode=nodes.get(i);
			if (cNode==node)println("identical");
			if (cNode.getX()==node.getX()&&cNode.getY()==node.getY()&&cNode.getZ()==node.getZ())return true;
		}
		return false;
	}
	public boolean isEquals(MeshNode n1,MeshNode n2){
		if (n1==n2){
			println("identical");
		}
		if (n1.getX()==n2.getX()&&n1.getY()==n2.getY()&&n1.getZ()==n2.getZ())return true;
		return false;
	}
	public void moveToCenter(MeshNode n1) {
		MeshNode[] nbs = n1.getNbs();
		Vec3D center = new Vec3D();
		// println("nbs: "+nbs.length);
		for (int j = 0; j < nbs.length; j++) {
			center.add(nbs[j]);
		}
		center.add(n1);
		center.mult(1f / (nbs.length + 1));
		n1.setPosition(center.getX(), center.getY(), n1.getZ());
	}
	public GeometryInfo  fillRings(Mesh meshInput,ArrayList<ArrayList<MeshNode>> rings,boolean up) {
		int[] stripCountArray = new int[rings.size()];

		ArrayList<MeshNode> nodes = new ArrayList<MeshNode>();
		for (int i = 0; i < rings.size(); i++) {
			ArrayList<MeshNode> ring = rings.get(i);
			if (i==0&&Polygon2D.getArea(ring)<0){
				Collections.reverse(ring);
			}
			if (i>0&&Polygon2D.getArea(ring)>0){
				Collections.reverse(ring);
			}
			stripCountArray[i] = ring.size();
			for (int j = 0; j < ring.size(); j++) {
				MeshNode node = ring.get(j);
				node.setId(nodes.size());
				nodes.add(node);
				
			}
		}
		
		float[] coordinates = new float[nodes.size() * 3];
		for (int i = 0; i < nodes.size(); i++) {
			MeshNode node = nodes.get(i);
			coordinates[i * 3] = node.x*100000f;
			coordinates[i * 3 + 1] = node.y*100000f;
			coordinates[i * 3 + 2] = 0f;
		}
		
		meshInput.rebuildIds();
		int[] newIds = new int[nodes.size()];
		for (int i = 0; i < nodes.size(); i++) {
			MeshNode node = nodes.get(i);
			newIds[i] = node.id;
		}
		println("startTriangulate");
		GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
		gi.setCoordinates(coordinates);
		int[] counturCountArray = { stripCountArray.length };
		gi.setContourCounts(counturCountArray);
		//gi.setContourCounts(stripCountArray);
		gi.setStripCounts(stripCountArray);
		gi.convertToIndexedTriangles();
		int[] indexes = gi.getCoordinateIndices();
		//Point3f[] pts = gi.getCoordinates();
		println("endTriangulate");
		for (int i = 0; i < indexes.length; i += 3) {
			if (up){
			meshInput.addTri(newIds[indexes[i]], newIds[indexes[i + 1]],
					newIds[indexes[i + 2]]);
			}
			else{
				meshInput.addTri(newIds[indexes[i+2]], newIds[indexes[i + 1]],
						newIds[indexes[i ]]);
			}
		}
		return gi;
	}
	class PolyPt extends PolygonPoint{
		MeshNode node;
		public PolyPt(double x, double y,MeshNode node) {
			super(x, y);
			this.node=node;
			// TODO Auto-generated constructor stub
		}
		
	}
}
